/*
 * ConcreteSplitViewer program for analazing splits.
 * Copyright (C) 2006-2007 Mytinski Leonid (Leonid.Mytinski@gmail.com)
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 * 
 */
package ru.concretesoft.concretesplitviewer;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Vector;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import ru.concretesoft.concretesplitviewer.Athlete;
import ru.concretesoft.concretesplitviewer.Distance;
import ru.concretesoft.concretesplitviewer.Group;
import ru.concretesoft.concretesplitviewer.NotRightFormatException;
import ru.concretesoft.concretesplitviewer.Time;
import ru.concretesoft.concretesplitviewer.Tools;

/**
 *
 * @author Valeri Mytinski
 * @date 2008-04-20
 * 
 * This reader is based on OCT2007reader.java.
 * Difference is: OCT2007reader ignores Athletes that has more ore less points
 * than first athletes has; this reader tries to interpolate missing points.
 * Interpolation is done in linear manner - as if athlete had equal physical
 * speeds on all missing laps.
 * 
 * So we need to know two things:
 * 1) Rignt number of points in Group
 * 2) Number of chars that each point occupies in input string
 * 
 * For this we use line of hypens.
 * 
 * We count file as OCT2007 if it consists of series "athlete-patterns".
 * Athlete-pattern is three lines like these:
 *    1   206 МАВЧУН       ГЕОРГИЙ      2000 кмс  СПбГУ                   33:58    04:16   05:17   06:30   08:27   13:50   20:01   22:55   29:37   31:12   32:14
 *                                                                                 04:16   01:01   01:13   01:57   05:23   06:11   02:54   06:42   01:35   01:02
 * -------------------------------------------------------------------------------------------------------------------------------------------------------------
 * 
 * When we find first athlete in group we calculate:
 * 1) Position where finish time ends - it is in the first line of pattern.
 * 2) Last position in the hypen line.
 * 3) Number of points as difference between second and first value divided by 8
 * as we stand that each point occupies 8 chars in line.
 * 
 * Than we parse first line for each Athlete as line with fixed width fields.
 * 
 */
public class OCT2007readerNew extends ru.concretesoft.concretesplitviewer.SplitReader {

    private File file;
    private FileInputStream fIS;
    private String all;
    private String nameOfComp;
    private Vector<String> groupsNames;
    private Vector<Group> allGroups;
    private String encoding = "CP1251";
    private int version = 0; // 0 - unknown version
    private String eventDescription = ""; // Event description if it presents in file
    private int numberOfPoints = 0; // Number of points in group
    private int finishTimeEnds = 0; // Index of last character of finish time

    /**
     * Creates a new instance of OCT2007Reader
     *
     * @param file splits file
     * @throws java.io.IOException
     * @throws ru.concretesoft.concretesplitviewer.NotRightFormatException
     */
    public OCT2007readerNew(File file) throws IOException, NotRightFormatException {
        this.file = file;
        fIS = new FileInputStream(file);

        int length = (int) file.length();
        byte[] s = null;
        try {
            s = new byte[length];
        } catch (java.lang.OutOfMemoryError e) {
            throw new IOException("File too long to fit into memory.");
        }

        fIS.read(s);
        try {
            all = new String(s, encoding);
        } catch (UnsupportedEncodingException e) {
            all = "";
        }

        /*
         * Check if there are two "athlete-pattern" - one just after another -
         * in the file.
         * If yes, it looks like OCT2007 format.
         * If no, we will not parse file.
         */
        String firstLineOfPattern = "[ \\t]*\\d+[ \\t]+\\d+[ \\t\\S]*([ \\t]+(\\d{1,2}:)?\\d\\d:\\d\\d)+[ \\t]*";
        String secondLineOfPattern = "([ \\t]+(\\d{1,2}:)?\\d\\d:\\d\\d)+[ \\t]*";
        String thirdLineOfPattern = "---+[ \\t]*";
        Pattern hyphenPattern = Pattern.compile(thirdLineOfPattern + "\\r\\n");
        Matcher hyphenMatcher = hyphenPattern.matcher(all);
        Pattern athletePattern = Pattern.compile(firstLineOfPattern + "\\r\\n" + secondLineOfPattern + "\\r\\n" + thirdLineOfPattern + "\\r\\n");
        Matcher athleteMatcher = athletePattern.matcher(all);
        if (!hyphenMatcher.find()) {
            throw new NotRightFormatException(file, "OCT2007", "it not contains line of hyphens");
        } else {
            if (!athleteMatcher.find()) {
                throw new NotRightFormatException(file, "OCT2007", "it not contains athlete pattern");
            } else {
                if (!athleteMatcher.find(athleteMatcher.end())) {
                    throw new NotRightFormatException(file, "OCT2007", "it not contains two sequential athlete pattern");
                }
            }
        }

        /*
         * OK. It seems OCT2007 format.
         * Try to parse it.
         */
        Pattern groupPattern = Pattern.compile("^\\s+([^-\\s:]+)\\s*(\\r\\n)+", Pattern.MULTILINE);
        Matcher groupMatcher = groupPattern.matcher(all);

        groupsNames = new Vector<String>();
        allGroups = new Vector<Group>();

        /*
         * Find group pattern, find next group pattern (or end of text)
         * Then find all athlete patterns between them.
         */
        int groupPatternIndex = 0;
        while (groupMatcher.find(groupPatternIndex)) {
            // New group begins
            allGroups.add(new Group());
            // Store it's name
            String groupName = groupMatcher.group(1);
            groupsNames.add(groupName);
            allGroups.lastElement().setName(groupName);

            // Parse athletes in group
            int curIndex = groupMatcher.start();
            int endIndex = 0;
            if (groupMatcher.find(groupMatcher.end())) {
                endIndex = groupMatcher.start();
            } else {
                endIndex = all.length() - 1;
            }

            boolean athleteFinded = true;
            this.numberOfPoints = 0;
            while ((curIndex < endIndex) && (athleteFinded)) {
                athleteFinded = false;
                if (athleteMatcher.find(curIndex)) {
                    curIndex = athleteMatcher.start();
                    if (curIndex < endIndex) {
                        String athleteMatcherGroup = athleteMatcher.group();
                        setNumberOfPointsInGroup(athleteMatcherGroup);
                        // Parse athlete
                        parseAthlete(athleteMatcherGroup, allGroups.lastElement());
                        athleteFinded = true;
                        curIndex = athleteMatcher.end();
                    }
                }
            }

            //Remove group if it doesn't have athletes
            if (allGroups.lastElement().getAthletes().size() == 0) {
                allGroups.remove(allGroups.lastElement());
                groupsNames.remove(groupsNames.lastElement());
            } else {
                // Store group's distance
                int groupDistance = 5000;
                Distance d = new Distance(groupName, groupDistance, this.numberOfPoints);
                allGroups.lastElement().setDistance(d);
                d.setLengthsOfDists(Tools.calculatLengthsOfLaps(d.getGroups()));
            }

            groupPatternIndex = endIndex;
        }
    }

    private void setNumberOfPointsInGroup(String athleteMatcherGroup) {
        if (this.numberOfPoints == 0) {
            // This is first athlete in a group.
            // Find numberOfPoints
            String[] lines = athleteMatcherGroup.split("\\r\\n");
            int lastIndex = lines[2].length();
            Pattern timePattern = Pattern.compile("\\d\\d:\\d\\d");
            Matcher timeMatcher = timePattern.matcher(athleteMatcherGroup);
            timeMatcher.find();
            this.finishTimeEnds = timeMatcher.end();
            this.numberOfPoints = (lastIndex - this.finishTimeEnds) / 8 + 1;
        }

    }

    /**
     * Parse string for athlete information.
     * Create new athlete and add it to group.
     *
     * @param s String - three lines - to parse
     * @param g Group for new athlete
     */
    private void parseAthlete(String s, Group g) {
        String[] lines = s.split("\\r\\n");

        // 1-st line is like
        //   1   206 МАВЧУН       ГЕОРГИЙ      2000 кмс  СПбГУ                   33:58    04:16   05:17   06:30   08:27   13:50   20:01   22:55   29:37   31:12   32:14
        // 2-nd line is like
        //                                                                                04:16   01:01   01:13   01:57   05:23   06:11   02:54   06:42   01:35   01:02
        // 3-d line is like
        // -------------------------------------------------------------------------------------------------------------------------------------------------------------

        // Catch last and first names from 1-st line - 3-d and 4-th words.
        String[] words = lines[0].trim().split("\\s++");
        String lastName = words[2];
        String firstName = words[3];
        String athleteResult = "24:00:00";

        // Then find first word that matches time pattern - it is the result time
        Pattern timePattern = Pattern.compile("(\\d{1,2}:)?\\d\\d:\\d\\d");
        Matcher timeMatcher = timePattern.matcher(lines[0]);
        timeMatcher.find();
        int athleteFinishTimeEnds = timeMatcher.end();
        // If athleteFinishTimeEnds != this.finishTimeEnds we suppose there is no result: DSQ, NOF etc.
        if (athleteFinishTimeEnds != this.finishTimeEnds) return;

        int firstTimeIndex = 3;
        while (firstTimeIndex < words.length) {
            if (words[firstTimeIndex].matches("(\\d{1,2}:)?\\d\\d:\\d\\d")) {
                athleteResult = words[firstTimeIndex];
                break;
            }
            firstTimeIndex++;
        }

        // Create array of all splits
        Time[] athleteSplits = new Time[this.numberOfPoints];

        // Now calc all splits from last to "second" (first presented lap time)
        int curIndex = lines[2].length() - 8;
        int splitCount = 1;
        int splitIndex = this.numberOfPoints - 1;
        String curAthleteResult = athleteResult;

        while (curIndex > this.finishTimeEnds) {
            String sTime = lines[0].substring(curIndex, curIndex + 8).trim();
            if ("".equals(sTime)) {
                splitCount++;
            } else {
                int seconds = stringToSeconds(curAthleteResult) - stringToSeconds(sTime);
                int secondsForSplit = seconds / splitCount;
                while (seconds >= 2 * secondsForSplit) {
                    String stringForSplit = secondsToString(secondsForSplit);
                    String[] fields = stringForSplit.split(":");
                    athleteSplits[splitIndex--] = new Time(stringForSplit, fields.length);
                    seconds -= secondsForSplit;
                }

                String stringForSplit = secondsToString(seconds);
                String[] fields = stringForSplit.split(":");
                athleteSplits[splitIndex--] = new Time(stringForSplit, fields.length);
                splitCount = 1;
                curAthleteResult = sTime;
            }
            curIndex -= 8;
        }

        // ... and from all not presented to first
        int seconds = stringToSeconds(curAthleteResult) - stringToSeconds("00:00");
        int secondsForSplit = seconds / splitCount;
        while (seconds >= 2 * secondsForSplit) {
            String stringForSplit = secondsToString(secondsForSplit);
            String[] fields = stringForSplit.split(":");
            athleteSplits[splitIndex--] = new Time(stringForSplit, fields.length);
            seconds -= secondsForSplit;
        }

        String stringForSplit = secondsToString(seconds);
        String[] fields = stringForSplit.split(":");
        athleteSplits[splitIndex--] = new Time(stringForSplit, fields.length);

        // U-u-ph
        Athlete a = new Athlete(lastName, firstName,
                athleteSplits, allGroups.lastElement(),
                1900,
                athleteResult);
    }

    private int stringToSeconds(String s) {
        String[] hhmmss = s.trim().split(":");
        int seconds = 0;
        for (int i = 0; i < hhmmss.length; i++) {
            seconds = 60 * seconds + Integer.parseInt(hhmmss[i]);
        }

        return seconds;
    }

    private String secondsToString(int seconds) {
        int h, m, s;
        s = seconds;
        h = s / 3600;
        s = s - 3600 * h;
        m = s / 60;
        s = s - 60 * m;
        return (h > 0 ? String.format("%02d:", h) : "") + String.format("%02d:", m) + String.format("%02d", s);
    }

    public Vector<String> getGroupsNames() {
        return groupsNames;
    }

    public String getFileName() {
        return file.getName();
    }

    public String getEventDescription() {
        return eventDescription;
    }

    @Override
    public Vector<Group> getAllGroups() {
        return allGroups;
    }

    @Override
    public Group getGroup(String name) {
        int index = groupsNames.indexOf(name);
        return allGroups.get(index);
    }

    @Override
    public Group getGroup(int index) {
        return allGroups.get(index);
    }

    @Override
    public Vector<Group> getGroupsByDist(int index) {
        return null;
    }
}
